package com.processor;

import com.annotation.Data;
import com.google.auto.service.AutoService;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.TypeTag;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.*;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import java.util.Set;

/**
 *SupportedSourceVersion(SourceVersion.RELEASE_8)
 *指定注解处理器支持的最低JAVA版本
 *
 *SupportedAnnotationTypes({"com.annotation.Data"})
 * 指定要处理的注解
 *
 *AutoService(Processor.class)
 * 谷歌自动注册成注解处理器：
 * 也可以自己在resources下建立META-INF/services文件夹，并建立javax.annotation.processing.Processor文件，
 * 文件中填写注解处理器的完整包名，例如com.processor.DataProcessor
 *
 * Debug注解处理器方法:
 * 1：注解处理器应该使用@SupportedOptions("debug")进行注解，表示支持调试
 * 2：在Debug Configuration中的Remote JVM Debug中新建调试，
 * 3：在Terminal的对应项目目录，输入命令mvnDebug clean install
 * 4：设置要调试的断点，点击Debug按钮
 *
 * ReCompile某个类时最好修改一下类，不然有时候不会重新编译
 */
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({"com.annotation.Data"})
@AutoService(Processor.class)
@SupportedOptions("debug")
public class DataProcessor extends AbstractProcessor {
    /**
     * 当该方法返回true时，表示该注解处理器所能处理的注解不需要其它注解处理器再进行处理
     * 返回false时，表示该注解处理器所能处理的注解其它注解处理器还可以再次进行处理
     * @param annotations
     * @param roundEnv
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        //获取所有被Data注解的元素
        Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Data.class);
        for(Element element : set){
            //如果Data注解不是在类上，则不做处理
            if(element.getKind() != ElementKind.CLASS){
                continue;
            }

            //获取语法树
            JCTree jcTree = trees.getTree(element);
            TreeTranslator treeTranslator = new TreeTranslator(){
                /**
                 * 语法树AST将类中的每个语法结构都定义为一个节点，处理不同的节点，就重写（Override）不同的visit
                 * @param tree 被Data注解的元素
                 */
                @Override
                public void visitClassDef(JCTree.JCClassDecl tree) {
                    //定义一个空的存放类中所有成员变量的集合
                    List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
                    //循环类中的所有语法树节点，找到所有的成员
                    for(JCTree tmpTree : tree.defs){
                        if(tmpTree.getKind().equals(Tree.Kind.VARIABLE)) {
                            JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tmpTree;
                            jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
                        }
                    }

                    //为每个成员变量生成get set方法
                    for(JCTree.JCVariableDecl jcVariableDecl : jcVariableDeclList){
                        tree.defs = tree.defs.prepend(createGetterMethodDecl(jcVariableDecl));
                        tree.defs = tree.defs.prepend(createSetterMethodDecl(jcVariableDecl));
                    }
                    super.visitClassDef(tree);
                }
            };
            jcTree.accept(treeTranslator);
        }
        return true;
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.trees = JavacTrees.instance(this.processingEnv);
        //因为在eclipse下采用的编译器不是javac，而不是eclipse自己开发的编译器，processingEnv并不是JavacProcessingEnvironment类型，也不是父子类关系，所以在eclipse下该处会有问题
        Context context = ((JavacProcessingEnvironment) this.processingEnv).getContext();
        this.treeMaker = TreeMaker.instance(context);
        this.names = Names.instance(context);
    }
    private JavacTrees trees;
    private TreeMaker treeMaker;
    private Names names;


    /**
     * 生成getter方法节点
     * @param jcVariableDecl
     * @return
     */
    private JCTree.JCMethodDecl createGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
        //生成public访问修饰符
        JCTree.JCModifiers jcModifiers = treeMaker.Modifiers(Flags.PUBLIC);

        //根据参数的类型生成方法的返回类型
        JCTree.JCExpression retrunType = jcVariableDecl.vartype;

        //生成getXXX方法名
        Name name = createGetterMethodName(jcVariableDecl);

        //生成 retrurn this.XXX语句
        JCTree.JCStatement jcStatement = treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.name));
        List<JCTree.JCStatement> statementList = List.nil();
        statementList = statementList.append(jcStatement);
        JCTree.JCBlock jcBlock = treeMaker.Block(0, statementList);

        //泛型参数列表
        List<JCTree.JCTypeParameter> methodGenericParams = List.nil();
        //参数列表
        List<JCTree.JCVariableDecl> parameters = List.nil();
        //异常抛出列表
        List<JCTree.JCExpression> throwsClauses = List.nil();

        //构建getter方法
        return treeMaker.MethodDef(jcModifiers, name, retrunType, methodGenericParams, parameters, throwsClauses, jcBlock, null);
    }

    /**
     * 生成setter方法节点
     * @param jcVariableDecl
     * @return
     */
    private JCTree.JCMethodDecl createSetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
        //生成public访问修饰符
        JCTree.JCModifiers jcModifiers = treeMaker.Modifiers(Flags.PUBLIC);
        //生成void返回类型
        JCTree.JCExpression retrunType = treeMaker.TypeIdent(TypeTag.VOID);

        //生成setXXX方法名
        Name name = createSetterMethodName(jcVariableDecl);

        List<JCTree.JCVariableDecl> parameters = List.nil();
        //生成形参
        JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER), jcVariableDecl.name, jcVariableDecl.vartype, null);
        //形参必须设置
        param.pos = jcVariableDecl.pos;
        parameters = parameters.append(param);

        //生成this.XXX = XXX语句
        JCTree.JCStatement statement = treeMaker.Exec(treeMaker.Assign(
                treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.name),
                treeMaker.Ident(jcVariableDecl.name)));
        List<JCTree.JCStatement> statementList = List.nil();
        statementList = statementList.append(statement);
        //生成代码块
        JCTree.JCBlock jcBlock = treeMaker.Block(0, statementList);

        //泛型参数列表
        List<JCTree.JCTypeParameter> methodGenericParams = List.nil();

        //抛出的异常
        List<JCTree.JCExpression> throwsClauses = List.nil();

        //最后构建setter方法
        return treeMaker.MethodDef(jcModifiers, name, retrunType, methodGenericParams, parameters, throwsClauses, jcBlock, null);
    }

    /**
     * 根据变量生成get方法名
     * @param jcVariableDecl
     * @return
     */
    private Name createGetterMethodName(JCTree.JCVariableDecl jcVariableDecl) {
        String varName = jcVariableDecl.getName().toString();
        StringBuilder methodName = new StringBuilder();
        methodName.append("get");
        //成员变量的第一个字母大写
        methodName.append(varName.substring(0, 1).toUpperCase());
        methodName.append(varName.substring(1));
        return names.fromString(methodName.toString());
    }

    /**
     * 根据变量生成set方法名
     * @param jcVariableDecl 变量
     * @return
     */
    private Name createSetterMethodName(JCTree.JCVariableDecl jcVariableDecl){
        String varName = jcVariableDecl.getName().toString();
        StringBuilder methodName = new StringBuilder();
        methodName.append("set");
        //成员变量的第一个字母大写
        methodName.append(varName.substring(0, 1).toUpperCase());
        methodName.append(varName.substring(1));
        return names.fromString(methodName.toString());
    }
}
